﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;

namespace IndoorLocalization
{
    /// <summary>
    /// Receives rays from sensors and combines them to find valid entities.
    /// </summary>
	[Serializable()]
    public abstract class SensorMerger : IDeserializationCallback {

        /// <summary>
        /// Saves all currently valid rays.
		/// </summary>
		[NonSerialized]
        protected List<SensorRay> rays;
        /// <summary>
        /// Saves all entites currently found by this merger.
		/// </summary>
		[NonSerialized]
		protected List<Entity> entities;

		/// <summary>
		/// Log when the last network ray was received to be able to remove forgotten rays.
		/// </summary>
		private Dictionary<Guid, DateTime> lastRayReceived = new Dictionary<Guid,DateTime>();

		protected String name;
		/// <summary>
		/// The name of this merger to be displayed in the GUI.
		/// Should be unique.
		/// </summary>
		public String Name {
			get { return name; }
			set { name = value; }
		}

		/// <summary>
		/// The amount of current rays.
		/// </summary>
		public int Rays {
			get { return rays.Count; }
		}

		/// <summary>
		/// The amount of current entities.
		/// </summary>
		public int Entities {
			get { return entities.Count; }
		}

		/// <summary>
		/// Called when the merger is deserialized.
		/// </summary>
		void IDeserializationCallback.OnDeserialization(Object sender) {
			StartMerger();
		}

		/// <summary>
		/// Indicates whether the merge thread of this merger is running.
		/// </summary>
		public abstract bool IsRunning();
		/// <summary>
		/// Indicates whether this merger is local or remote.
		/// </summary>
		public abstract bool IsLocal();

		/// <summary>
		/// Initializes the important attributes and starts the merge thread.
		/// </summary>
		protected abstract void StartMerger();

		/// <summary>
		/// [Threadsafe] Removes all rays that were received via the network.
		/// </summary>
		public void ClearRemoteRaysTS() {
			lock (rays) {
				rays.RemoveAll(new Predicate<SensorRay>(target => {
					return target.CreatorCam == null;
				}));
			}
		}

		/// <summary>
		/// [Threadsafe] Removes all rays that were created by the given sensor.
		/// Should be used when pausing or disposing a sensor.
		/// </summary>
		/// <param name="sensor">Guid of the sensor of which rays should be removed.</param>
		public void RemoveAllRaysTS(Guid sensor) {
			lock (rays) {
				for (int i = 0; i < rays.Count; i++) {
					if (rays[i].Creator.Equals(sensor))
						rays.Remove(rays[i--]);
				}
			}
		}

        /// <summary>
        /// Removes all rays that were created by the given sensor.
        /// Should be used before adding new rays when updating.
        /// </summary>
		/// <param name="sensor">Guid of the sensor of which rays should be removed.</param>
		protected void RemoveAllRays(Guid sensor) {
			for (int i = 0; i < rays.Count; i++) {
				if (rays[i].Creator.Equals(sensor))
					rays.Remove(rays[i--]);
			}
        }

        /// <summary>
        /// Adds new rays to this merger.
        /// </summary>
        /// <param name="newRays">Rays to add.</param>
		protected void AddRays(List<SensorRay> newRays) {
			foreach (SensorRay ray in newRays)
				rays.Add(ray);
		}

		/// <summary>
		/// [Threadsafe] Updates the valid rays of the given sensor
		/// by deleting all old rays and adding the new ones.
		/// </summary>
		/// <param name="sensor">Guid of the sensor of which the rays should be updated.</param>
		/// <param name="newRays">List of currently valid rays.</param>
		public virtual void UpdateRaysTS(Guid sensor, List<SensorRay> newRays) {
			// update last active time
			if (lastRayReceived.ContainsKey(sensor))
				lastRayReceived[sensor] = DateTime.Now;
			else
				lastRayReceived.Add(sensor, DateTime.Now);

			// collect rays that are too old
			List<Guid> toRemove = new List<Guid>();
			toRemove.Add(sensor);
			try {
				foreach (KeyValuePair<Guid, DateTime> entry in lastRayReceived) {
					if (DateTime.Now.TimeOfDay.Subtract(TimeSpan.FromSeconds(2)).CompareTo(entry.Value.TimeOfDay) > 0)
						toRemove.Add(entry.Key);
				}
			} catch (InvalidOperationException) {
				// if list has changed, remove nothing because another thread is doing the work now
				toRemove.Clear();
				toRemove.Add(sensor);
			}

			// remove all rays that are too old, all rays of the given sensor
			// and add the new rays of this sensor
			lock (rays) {
				foreach (Guid guid in toRemove) {
					RemoveAllRays(guid);
				}
				AddRays(newRays);
			}
		}

        /// <summary>
        /// [Threadsafe] Returns an (independent) list of all entities
        /// currently found by this merger.
        /// </summary>
        /// <returns>A list of currently found entities.</returns>
        public List<Entity> GetEntitiesTS() {
            List<Entity> result = new List<Entity>();
            lock (entities) {
                foreach (Entity entity in entities)
                    result.Add(entity);
            }
            return result;
        }

		/// <summary>
		/// [Threadsafe] Returns an (independent) list of all rays
		/// reported to this merger.
		/// </summary>
		/// <returns>A list of all rays.</returns>
		public List<SensorRay> GetRaysTS() {
			List<SensorRay> result = new List<SensorRay>();
			lock (rays) {
				foreach (SensorRay ray in rays)
					result.Add(ray);
			}
			return result;
		}

    }
}
